// Copyright 2022 jeff.li. and/or its affiliates.

#pragma once

#include <tuple>

#include <tbir/runtime/object.h>

namespace tbir::runtime {

    class Unicode;

    class String;
}  // tbir::runtime
namespace tbir::runtime::details {

    template<typename... ARGS>
    struct is_builtins_str_type;

    template<typename T, typename... ARGS>
    struct is_builtins_str_type<T, ARGS...> {
        using PURE_T = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
        static constexpr bool value =
                (std::is_same<Unicode, PURE_T>::value || std::is_same<String, PURE_T>::value ||
                 std::is_same<unicode_view, PURE_T>::value || std::is_same<string_view, PURE_T>::value) &&
                is_builtins_str_type<ARGS...>::value;
    };

    template<typename T>
    struct is_builtins_str_type<T> {
        using PURE_T = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
        static constexpr bool value =
                (std::is_same<Unicode, PURE_T>::value || std::is_same<String, PURE_T>::value ||
                 std::is_same<unicode_view, PURE_T>::value || std::is_same<string_view, PURE_T>::value);
    };

    template<typename... ARGS>
    struct is_object_type;

    template<typename T, typename... ARGS>
    struct is_object_type<T, ARGS...> {
        static constexpr bool value =
                std::is_base_of<
                        ObjectRef,
                        typename std::remove_cv<typename std::remove_reference<T>::type>::type>::value &&
                is_object_type<ARGS...>::value;
    };

    template<typename T>
    struct is_object_type<T> {
        static constexpr bool value = std::is_base_of<
                ObjectRef,
                typename std::remove_cv<typename std::remove_reference<T>::type>::type>::value;
    };

    template<bool as_tuple, class T, class... Others>
    struct ObjectsHolder;

    template<class T, class... Others>
    struct ObjectsHolder<true, T, Others...> {
        static_assert(is_object_type<T, Others...>::value || is_builtins_str_type<T, Others...>::value,
                      "not a object reference or str");
        std::tuple<typename std::remove_cv<typename std::remove_reference<T>::type>::type,
                typename std::remove_cv<typename std::remove_reference<Others>::type>::type...>
                values;

        ObjectsHolder() = default;

        ObjectsHolder(T const &v, Others const &... o) : values(v, o...) {
        }
    };

    template<class T>
    struct ObjectsHolder<false, T> {
        typename std::remove_cv<typename std::remove_reference<T>::type>::type values;

        ObjectsHolder() = default;

        ObjectsHolder(T const &v) : values(v) {
        }
    };

    template<class T>
    struct ObjectsHolder<true, T> {
        std::tuple<typename std::remove_cv<typename std::remove_reference<T>::type>::type> values;

        ObjectsHolder() = default;

        ObjectsHolder(T const &v) : values(v) {
        }
    };

}  // namespace  tbir::runtime::details
